package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.constants.RedisConstant;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.po.PointsBoardHistory;
import com.tianji.learning.domain.po.PointsBoardSeason;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardHistoryService;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsBoardService;
import com.tianji.learning.util.TableInfoContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author xjs
 */
@Service
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    private UserClient userClient;

    @Autowired
    private IPointsBoardSeasonService seasonService;

    @Autowired
    private IPointsBoardHistoryService historyService;

    @Override
    public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery query) {
        Long userId = UserContext.getUser();
        // 判断查询当前还是历史排行榜
        Long season = query.getSeason();
        boolean isCurrent = (season == null || season <= 0);
        PointsBoardVO vo = new PointsBoardVO();
        if(isCurrent){
            // 如果是当前
            String boardKey = getCurrentBoardKey();
            //  查询我的积分 我的排名, rank默认按照分值大小从小打到排，从0开始
            Long rank = redisTemplate.opsForZSet().reverseRank(boardKey, "" + userId);
            Double score = redisTemplate.opsForZSet().score(boardKey, "" + userId);
            vo.setRank(rank==null?0:rank.intValue()+1);
            vo.setPoints(score==null?0:score.intValue());
            //  分页查询排行榜
            List<PointsBoard> pointsBoards = pageQueryFromZSet(boardKey, query.getPageNo(), query.getPageSize());
            if(!CollUtils.isEmpty(pointsBoards)){
                vo.setBoardList(poToVoList(pointsBoards, query.getPageNo(), query.getPageSize()));
            }
        }else{
            //  查询历史
            // 设置实际的表名
            String tableName = "points_board_"+season;
            //  查询我的积分 我的排名
            PointsBoard myBoard = this.baseMapper.queryByUserId(tableName, userId);
            if(myBoard != null){
                vo.setRank(myBoard.getId().intValue());
                vo.setPoints(myBoard.getPoints());
            }
            //  分页查询排行
            List<PointsBoard> records = this.baseMapper.pageQuery(new Page<>(query.getPageNo(), query.getPageSize()), tableName);
            if(CollUtils.isNotEmpty(records)){
                // po -> vo
                vo.setBoardList(poToVoList(records, query.getPageNo(), query.getPageSize()));
            }
        }
        return vo;
    }

    @Override
    public void createPointsBoardTable() {
        // 查询上一个赛季
        PointsBoardSeason season = queryLastSeason();
        // 拼接得到历史榜单的表名:points_board_seasonId
        String tableName= "points_board_" + season.getId();
        //  CREATE TABLE IF NOT EXISTS
        this.baseMapper.createPointsBoardTable(tableName);
    }

    @Override
    public void savePointsBoard2DB() {
        // 查询上一个赛季
        PointsBoardSeason season = queryLastSeason();
        //拿到上一个月的榜单数据
        String boardKey = getLastMonthBoardKey();
        // 分页查询zset
        int pageNo = 1;
        int pageSize = 100;
        while(true){
            // 分页查询
            List<PointsBoard> pointsBoards = pageQueryFromZSet(boardKey, pageNo, pageSize);
            if(CollUtils.isEmpty(pointsBoards)){
                break;
            }
            String tableName = "points_board_"+season.getId();
            // 方法1：可以直接使用SQL语句
            // this.baseMapper.batchInsert(tableName, pointsBoards);
            // 方法2：也可以使用动态表名的插件
            // 转化成历史表的po对象
            List<PointsBoardHistory> boardHistoryList = new ArrayList<>();
            for (PointsBoard pointsBoard : pointsBoards) {
                boardHistoryList.add(BeanUtils.copyBean(pointsBoard, PointsBoardHistory.class));
            }
            // 设置实际的表名
            TableInfoContext.setInfo(tableName);
            // 批量插入到mysql, mp动态表名插件
            historyService.saveBatch(boardHistoryList);
            // 清理实际的表名
            TableInfoContext.remove();
            pageNo++;
        }
    }

    @Override
    public void clearPointsBoardFromRedis() {
        //拿到上一个月的榜单数据
        String boardKey = getLastMonthBoardKey();
        // 删除key
        redisTemplate.unlink(boardKey);
    }


    private PointsBoardSeason queryLastSeason(){
        LocalDateTime lastMonth = LocalDateTime.now().minusMonths(1);
        // 查询上一个月的赛季id
        PointsBoardSeason season = seasonService.lambdaQuery().le(PointsBoardSeason::getBeginTime, lastMonth)
                .ge(PointsBoardSeason::getEndTime, lastMonth).one();
        if(season == null){
            throw new BizIllegalException("赛季不存在");
        }
        return season;
    }

    private List<PointsBoardItemVO> poToVoList(List<PointsBoard> pointsBoards, int pageNo, int pageSize){
        // 获取用户列表
        Map<Long, UserDTO> userMap = getUserMap(pointsBoards);
        // 封装分页数据
        List<PointsBoardItemVO> boardList = new ArrayList<>();
        int idx = 1;
        for (PointsBoard board : pointsBoards) {
            PointsBoardItemVO itemVO= new PointsBoardItemVO();
            // 积分
            itemVO.setPoints(board.getPoints());
            // 排名
            itemVO.setRank((pageNo-1)*pageSize + idx);
            // 用户
            UserDTO userDTO = userMap.get(board.getUserId());
            if(userDTO == null){
                throw new BizIllegalException("用户不存在:"+board.getUserId());
            }
            itemVO.setName(userDTO.getName());
            boardList.add(itemVO);
            idx++;
        }
        return boardList;
    }

    private List<PointsBoard> pageQueryFromZSet(String boardKey, int pageNo, int pageSize){
        int start = (pageNo-1)* pageSize;
        int end = start + pageSize -1;
        // 分页查询
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(boardKey, start, end);
        if(CollUtils.isEmpty(typedTuples)){
            return null;
        }
        List<PointsBoard> boards = new ArrayList<>();
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            PointsBoard board = new PointsBoard();
            board.setUserId(Long.parseLong(typedTuple.getValue()));
            board.setPoints(typedTuple.getScore().intValue());
            boards.add(board);
        }
        return boards;
    }

    private String getLastMonthBoardKey(){
        //上一个月的榜单数据
        String yyyyMM = LocalDateTime.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM"));
        return RedisConstant.BOARD_PREFIX + yyyyMM;
    }

    private String getCurrentBoardKey(){
        String yyyyMM = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
        return RedisConstant.BOARD_PREFIX + yyyyMM;
    }

    private Map<Long, UserDTO> getUserMap(List<PointsBoard> boards){
        List<Long> userIds = boards.stream().map(bd -> bd.getUserId()).distinct().collect(Collectors.toList());
        List<UserDTO> users = userClient.queryUserByIds(userIds);
        if(CollUtils.isEmpty(users)){
            throw new BizIllegalException("查询不到用户信息:"+userIds);
        }
        return users.stream().collect(Collectors.toMap(UserDTO::getId, u -> u));
    }

    private Map<Long, UserDTO> getUserMap(Set<ZSetOperations.TypedTuple<String>> typedTuples){
        List<Long> userIds = typedTuples.stream().map(tp -> Long.parseLong(tp.getValue())).distinct().collect(Collectors.toList());
        List<UserDTO> users = userClient.queryUserByIds(userIds);
        if(CollUtils.isEmpty(users)){
            throw new BizIllegalException("查询不到用户信息:"+userIds);
        }
        return users.stream().collect(Collectors.toMap(UserDTO::getId, u -> u));
    }
}
